/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <rango@swoole.com>                             |
 +----------------------------------------------------------------------+
 */

#include "swoole_string.h"
#include "swoole_socket.h"
#include "swoole_ssl.h"
#include "swoole_util.h"

#ifdef SW_USE_OPENSSL

using swoole::SSLContext;
using swoole::network::Address;
using swoole::network::Socket;

#if OPENSSL_VERSION_NUMBER < 0x10100000L
#error "OpenSSL 1.1.0 or later is required"
#endif

static bool openssl_init = false;
static int ssl_connection_index = 0;
static int ssl_port_index = 0;

static int swoole_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store);
#if OPENSSL_VERSION_NUMBER < 0x10100000L
static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context);
#endif

#ifdef SW_SUPPORT_DTLS
static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len);
static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len);
#endif

std::string swoole_ssl_get_version_message() {
    return swoole::std_string::format("OPENSSL_VERSION: %s\n", OPENSSL_VERSION_TEXT);
}

void swoole_ssl_init() {
    if (openssl_init) {
        return;
    }

    if (!OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG | OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_LOAD_CRYPTO_STRINGS,
                          nullptr)) {
        swoole_error("OPENSSL_init_ssl() failed");
        return;
    }

    ssl_connection_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
    if (ssl_connection_index < 0) {
        swoole_error("SSL_get_ex_new_index() failed");
        return;
    }

    ssl_port_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
    if (ssl_port_index < 0) {
        swoole_error("SSL_get_ex_new_index() failed");
        return;
    }

    openssl_init = true;
}

int swoole_ssl_get_ex_connection_index() {
    return ssl_connection_index;
}

int swoole_ssl_get_ex_port_index() {
    return ssl_port_index;
}

void swoole_ssl_destroy() {
    if (!openssl_init) {
        return;
    }
    openssl_init = false;
}

static int ssl_error_cb(const char *str, size_t len, void *buf) {
    auto s = static_cast<swoole::String *>(buf);
    memcpy(s->str, str, len);
    s->length = len;
    s->set_null_terminated();
    return 0;
}

const char *swoole_ssl_get_error() {
    sw_tg_buffer()->clear();
    sw_tg_buffer()->set_null_terminated();
    ERR_print_errors_cb(ssl_error_cb, sw_tg_buffer());
    return sw_tg_buffer()->str;
}

static void swoole_ssl_info_callback(const SSL *ssl, int where, int ret) {
    Socket *sock;

    if (where & SSL_CB_HANDSHAKE_START) {
        sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index);

        if (sock->ssl_state == SW_SSL_STATE_READY) {
            sock->ssl_renegotiation = 1;
            swoole_debug("SSL renegotiation");
        }
    }

    if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) {
        sock = (Socket *) SSL_get_ex_data(ssl, ssl_connection_index);

        if (!sock->ssl_handshake_buffer_set) {
            /*
             * By default, OpenSSL uses 4k buffer during a handshake,
             * which is too low for long certificate chains and might
             * result in extra round-trips.
             *
             * To adjust a buffer size we detect that buffering was added
             * to write side of the connection by comparing rbio and wbio.
             * If they are different, we assume that it's due to buffering
             * added to wbio, and set buffer size.
             */
            BIO *rbio = SSL_get_rbio(ssl);
            BIO *wbio = SSL_get_wbio(ssl);

            if (rbio != wbio) {
                (void) BIO_set_write_buffer_size(wbio, SW_SSL_BUFFER_SIZE);
                sock->ssl_handshake_buffer_set = 1;
            }
        }
    }
}

namespace swoole {

#define HTTP2_H2_ALPN "\x02h2"
#define HTTP2_H2_16_ALPN "\x05h2-16"
#define HTTP2_H2_14_ALPN "\x05h2-14"
#define HTTP1_NPN "\x08http/1.1"

#define ssl_error(_fmt, ...)                                                                                           \
    long _ssl_error = ERR_get_error();                                                                                 \
    swoole_warning(_fmt ", Error: %s[%ld]", ##__VA_ARGS__, ERR_reason_error_string(_ssl_error), _ssl_error);

#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
static int ssl_alpn_advertised(SSL *ssl, const uchar **out, uchar *outlen, const uchar *in, uint32_t inlen, void *arg) {
    unsigned int protos_len;
    const char *protos;

    auto *cfg = (SSLContext *) arg;
    if (cfg->http_v2) {
        protos = HTTP2_H2_ALPN HTTP1_NPN;
        protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1;
    } else {
        protos = HTTP1_NPN;
        protos_len = sizeof(HTTP1_NPN) - 1;
    }

    if (SSL_select_next_proto((uchar **) out, outlen, (const uchar *) protos, protos_len, in, inlen) !=
        OPENSSL_NPN_NEGOTIATED) {
        return SSL_TLSEXT_ERR_NOACK;
    }
    return SSL_TLSEXT_ERR_OK;
}
#endif

static int ssl_passwd_callback(char *buf, int num, int verify, void *data) {
    const auto *ctx = static_cast<SSLContext *>(data);
    if (!ctx->passphrase.empty()) {
        const int len = static_cast<int>(ctx->passphrase.length());
        if (len < num - 1) {
            memcpy(buf, ctx->passphrase.c_str(), len);
            buf[len] = '\0';
            return len;
        }
    }
    return 0;
}

bool SSLContext::create() {
    if (!openssl_init) {
        swoole_ssl_init();
    }

    const SSL_METHOD *method;
#ifdef SW_SUPPORT_DTLS
    if (protocols & SW_SSL_DTLS) {
        method = DTLS_method();
    } else
#endif
    {
        method = SSLv23_method();
    }
    if (protocols == 0) {
        protocols = SW_SSL_ALL;
    }
    context = SSL_CTX_new(method);
    if (context == nullptr) {
        ssl_error("SSL_CTX_new() failed");
        return false;
    }

#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
    SSL_CTX_set_options(context, SSL_OP_MICROSOFT_SESS_ID_BUG);
#endif

#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
    SSL_CTX_set_options(context, SSL_OP_NETSCAPE_CHALLENGE_BUG);
#endif

    /* server side options */
#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
    SSL_CTX_set_options(context, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
#endif

#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
    SSL_CTX_set_options(context, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
#endif

#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
    /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
    SSL_CTX_set_options(context, SSL_OP_MSIE_SSLV2_RSA_PADDING);
#endif

#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
    SSL_CTX_set_options(context, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
#endif

#ifdef SSL_OP_TLS_D5_BUG
    SSL_CTX_set_options(context, SSL_OP_TLS_D5_BUG);
#endif

#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
    SSL_CTX_set_options(context, SSL_OP_TLS_BLOCK_PADDING_BUG);
#endif

#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
    SSL_CTX_set_options(context, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif

#if OPENSSL_VERSION_NUMBER >= 0x009080dfL
    /* only in 0.9.8m+ */
    SSL_CTX_clear_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1);
#endif

#ifdef SSL_OP_NO_SSLv2
    if (!(protocols & SW_SSL_SSLv2)) {
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv2);
    }
#endif
#ifdef SSL_OP_NO_SSLv3
    if (!(protocols & SW_SSL_SSLv3)) {
        SSL_CTX_set_options(context, SSL_OP_NO_SSLv3);
    }
#endif
#ifdef SSL_OP_NO_TLSv1
    if (!(protocols & SW_SSL_TLSv1)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_1
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_1);
    if (!(protocols & SW_SSL_TLSv1_1)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_1);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_2
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_2);
    if (!(protocols & SW_SSL_TLSv1_2) && !(protocols & SW_SSL_DTLS)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_2);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_3
    SSL_CTX_clear_options(context, SSL_OP_NO_TLSv1_3);
    if (!(protocols & SW_SSL_TLSv1_3)) {
        SSL_CTX_set_options(context, SSL_OP_NO_TLSv1_3);
    }
#endif

#ifdef SSL_OP_NO_COMPRESSION
    if (disable_compress) {
        SSL_CTX_set_options(context, SSL_OP_NO_COMPRESSION);
    }
#endif

#ifdef SSL_MODE_RELEASE_BUFFERS
    SSL_CTX_set_mode(context, SSL_MODE_RELEASE_BUFFERS);
#endif

#ifdef SSL_MODE_NO_AUTO_CHAIN
    SSL_CTX_set_mode(context, SSL_MODE_NO_AUTO_CHAIN);
#endif

    SSL_CTX_set_read_ahead(context, 1);
    SSL_CTX_set_info_callback(context, swoole_ssl_info_callback);

    if (!passphrase.empty()) {
        SSL_CTX_set_default_passwd_cb_userdata(context, this);
        SSL_CTX_set_default_passwd_cb(context, ssl_passwd_callback);
    }

    if (!cert_file.empty()) {
        /*
         * set the local certificate from CertFile
         */
        if (SSL_CTX_use_certificate_file(context, cert_file.c_str(), SSL_FILETYPE_PEM) <= 0) {
            ssl_error("SSL_CTX_use_certificate_file(%s) failed", cert_file.c_str());
            return true;
        }
        /*
         * if the crt file have many certificate entry ,means certificate chain
         * we need call this function
         */
        if (SSL_CTX_use_certificate_chain_file(context, cert_file.c_str()) <= 0) {
            ssl_error("SSL_CTX_use_certificate_chain_file(%s) failed", cert_file.c_str());
            return false;
        }
    }
    if (!key_file.empty()) {
        /*
         * set the private key from KeyFile (maybe the same as CertFile)
         */
        if (SSL_CTX_use_PrivateKey_file(context, key_file.c_str(), SSL_FILETYPE_PEM) <= 0) {
            ssl_error("SSL_CTX_use_PrivateKey_file(%s) failed", key_file.c_str());
            return false;
        }
        /*
         * verify private key
         */
        if (!SSL_CTX_check_private_key(context)) {
            ssl_error("SSL_CTX_check_private_key() failed");
            return false;
        }
    }

#ifdef SW_SUPPORT_DTLS
    if (protocols & SW_SSL_DTLS) {
#ifndef OPENSSL_IS_BORINGSSL
        SSL_CTX_set_cookie_generate_cb(context, swoole_ssl_generate_cookie);
        SSL_CTX_set_cookie_verify_cb(context, swoole_ssl_verify_cookie);
#endif
    }
#endif

    auto rs = set_capath();
    if (verify_peer) {
        if (!rs) {
            return false;
        }
    } else {
        SSL_CTX_set_verify(context, SSL_VERIFY_NONE, nullptr);
    }

    if (http || http_v2) {
        unsigned int protos_len;
        const char *protos;
        if (http_v2) {
            protos = HTTP2_H2_ALPN HTTP1_NPN;
            protos_len = sizeof(HTTP2_H2_ALPN HTTP1_NPN) - 1;
        } else {
            protos = HTTP1_NPN;
            protos_len = sizeof(HTTP1_NPN) - 1;
        }
        if (SSL_CTX_set_alpn_protos(context, (const uchar *) protos, protos_len) < 0) {
            ssl_error("SSL_CTX_set_alpn_protos(%s) failed", protos);
            return false;
        }
#ifdef TLSEXT_TYPE_application_layer_protocol_negotiation
        SSL_CTX_set_alpn_select_cb(context, ssl_alpn_advertised, (void *) this);
#endif
        SSL_CTX_set_session_id_context(context, (const uchar *) "HTTP", sizeof("HTTP") - 1);
        SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_SERVER);
    }

#ifdef OPENSSL_IS_BORINGSSL
    SSL_CTX_set_grease_enabled(context, grease);
#endif

    if (!client_cert_file.empty() && !set_client_certificate()) {
        return false;
    }

    if (!set_ciphers()) {
        swoole_warning("set_cipher() error");
        return false;
    }

    return true;
}

bool SSLContext::set_capath() const {
    if (!cafile.empty() || !capath.empty()) {
        const char *_cafile = cafile.empty() ? nullptr : cafile.c_str();
        const char *_capath = capath.empty() ? nullptr : capath.c_str();
        if (!SSL_CTX_load_verify_locations(context, _cafile, _capath)) {
            return false;
        }
    } else {
        if (!SSL_CTX_set_default_verify_paths(context)) {
            ssl_error("SSL_CTX_set_default_verify_paths() failed");
            return false;
        }
    }

    if (verify_depth > 0) {
        SSL_CTX_set_verify_depth(context, verify_depth);
    }

    return true;
}

bool SSLContext::set_ciphers() const {
#ifndef TLS1_2_VERSION
    return true;
#endif

    if (!ciphers.empty()) {
        if (SSL_CTX_set_cipher_list(context, ciphers.c_str()) == 0) {
            ssl_error("SSL_CTX_set_cipher_list(\"%s\") failed", ciphers.c_str());
            return false;
        }
        if (prefer_server_ciphers && !SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE)) {
            ssl_error("SSL_CTX_set_options(SSL_OP_CIPHER_SERVER_PREFERENCE) failed");
            return false;
        }
    }

    if (!dhparam.empty() && !set_dhparam()) {
        return false;
    }
#if OPENSSL_VERSION_NUMBER < 0x10100000L
    else {
        swoole_ssl_set_default_dhparam(context);
    }
#endif
    if (!ecdh_curve.empty() && !set_ecdh_curve()) {
        return false;
    }
    return true;
}

bool SSLContext::set_client_certificate() const {
    const char *_cert_file = client_cert_file.c_str();
    int depth = verify_depth;

    SSL_CTX_set_verify(context, SSL_VERIFY_PEER, swoole_ssl_verify_callback);
    SSL_CTX_set_verify_depth(context, depth);

    if (SSL_CTX_load_verify_locations(context, _cert_file, nullptr) == 0) {
        ssl_error("SSL_CTX_load_verify_locations(\"%s\") failed", _cert_file);
        return false;
    }

    ERR_clear_error();
    STACK_OF(X509_NAME) *list = SSL_load_client_CA_file(_cert_file);
    if (list == nullptr) {
        ssl_error("SSL_load_client_CA_file(\"%s\") failed", _cert_file);
        return false;
    }

    ERR_clear_error();
    SSL_CTX_set_client_CA_list(context, list);

    return true;
}

bool SSLContext::set_ecdh_curve() const {
#ifndef OPENSSL_NO_ECDH
    /*
     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
     * from RFC 4492 section 5.1.1, or explicitly described curves over
     * binary fields.  OpenSSL only supports the "named curves", which provide
     * maximum interoperability.
     */
#if (defined SSL_CTX_set1_curves_list || defined SSL_CTRL_SET_CURVES_LIST)
    /*
     * OpenSSL 1.0.2+ allows configuring a curve list instead of a single
     * curve previously supported.  By default, an internal list is used,
     * with prime256v1 being preferred by server in OpenSSL 1.0.2b+
     * and X25519 in OpenSSL 1.1.0+.
     *
     * By default, a curve preferred by the client will be used for
     * key exchange.  The SSL_OP_CIPHER_SERVER_PREFERENCE option can
     * be used to prefer server curves instead, similar to what it
     * does for ciphers.
     */
    SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
#if SSL_CTRL_SET_ECDH_AUTO
    /* not needed in OpenSSL 1.1.0+ */
    SSL_CTX_set_ecdh_auto(context, 1);
#endif
    if (strcmp(ecdh_curve.c_str(), "auto") == 0) {
        return true;
    }
    if (SSL_CTX_set1_curves_list(context, ecdh_curve.c_str()) == 0) {
        swoole_warning("SSL_CTX_set1_curves_list(\"%s\") failed", ecdh_curve.c_str());
        return false;
    }
#else
    EC_KEY *ecdh;
    /*
     * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
     * from RFC 4492 section 5.1.1, or explicitly described curves over
     * binary fields. OpenSSL only supports the "named curves", which provide
     * maximum interoperability.
     */
    int nid = OBJ_sn2nid(ecdh_curve.c_str());
    if (nid == 0) {
        swoole_warning("Unknown curve name \"%s\"", ecdh_curve.c_str());
        return false;
    }

    ecdh = EC_KEY_new_by_curve_name(nid);
    if (ecdh == nullptr) {
        swoole_warning("Unable to create curve \"%s\"", ecdh_curve.c_str());
        return false;
    }

    SSL_CTX_set_options(context, SSL_OP_SINGLE_ECDH_USE);
    SSL_CTX_set_tmp_ecdh(context, ecdh);

    EC_KEY_free(ecdh);
#endif
#endif

    return true;
}

bool SSLContext::set_dhparam() const {
    const char *file = dhparam.c_str();

    BIO *bio = BIO_new_file(file, "r");
    if (bio == nullptr) {
        ssl_error("BIO_new_file(%s) failed", file);
        return false;
    }

#if OPENSSL_VERSION_MAJOR >= 3
    EVP_PKEY *pkey = PEM_read_bio_Parameters(bio, nullptr);
    if (pkey == nullptr) {
        ssl_error("PEM_read_bio_Parameters('%s') failed", file);
        BIO_free(bio);
        return false;
    }

    if (SSL_CTX_set0_tmp_dh_pkey(context, pkey) != 1) {
        ssl_error("SSL_CTX_set0_tmp_dh_pkey('%s') failed", file);
        EVP_PKEY_free(pkey);
        BIO_free(bio);
        return false;
    }
#else
    DH *dh = PEM_read_bio_DHparams(bio, nullptr, nullptr, nullptr);
    if (dh == nullptr) {
        ssl_error("PEM_read_bio_DHparams(%s) failed", file);
        BIO_free(bio);
        return false;
    }

    SSL_CTX_set_tmp_dh(context, dh);

    DH_free(dh);
#endif

    BIO_free(bio);

    return true;
}

SSLContext::~SSLContext() {
    SSL_CTX_free(context);
}

}  // namespace swoole

static int swoole_ssl_verify_callback(int ok, X509_STORE_CTX *x509_store) {
#if 0
    char *subject, *issuer;
    int err, depth;
    X509 *cert;
    X509_NAME *sname, *iname;
    X509_STORE_CTX_get_ex_data(x509_store, SSL_get_ex_data_X509_STORE_CTX_idx());
    cert = X509_STORE_CTX_get_current_cert(x509_store);
    err = X509_STORE_CTX_get_error(x509_store);
    depth = X509_STORE_CTX_get_error_depth(x509_store);

    sname = X509_get_subject_name(cert);
    subject = sname ? X509_NAME_oneline(sname, nullptr, 0) : "(none)";

    iname = X509_get_issuer_name(cert);
    issuer = iname ? X509_NAME_oneline(iname, nullptr, 0) : "(none)";
    swoole_warning("verify:%d, error:%d, depth:%d, subject:\"%s\", issuer:\"%s\"", ok, err, depth, subject, issuer);

    if (sname)
    {
        OPENSSL_free(subject);
    }
    if (iname)
    {
        OPENSSL_free(issuer);
    }
#endif

    return 1;
}

#ifdef SW_SUPPORT_DTLS

#define COOKIE_SECRET_LENGTH (32)

static void calculate_cookie(SSL *ssl, uchar *cookie_secret, uint cookie_length) {
    long rv = (long) ssl;
    long inum = (cookie_length - (((long) cookie_secret) % sizeof(long))) / sizeof(long);
    long i = 0;
    long *ip = (long *) cookie_secret;
    for (i = 0; i < inum; ++i, ++ip) {
        *ip = rv;
    }
}

static int swoole_ssl_generate_cookie(SSL *ssl, uchar *cookie, uint *cookie_len) {
    uchar result[EVP_MAX_MD_SIZE];
    uint length = 0, result_len;
    Address sa{};

    uchar cookie_secret[COOKIE_SECRET_LENGTH];
    calculate_cookie(ssl, cookie_secret, sizeof(cookie_secret));

    /* Read peer information */
    (void) BIO_dgram_get_peer(SSL_get_wbio(ssl), &sa);

    length = 0;
    switch (sa.addr.ss.sa_family) {
    case AF_INET:
        length += sizeof(struct in_addr);
        break;
    case AF_INET6:
        length += sizeof(struct in6_addr);
        break;
    default:
        OPENSSL_assert(0);
        break;
    }

    length += sizeof(in_port_t);
    auto *buffer = (uchar *) OPENSSL_malloc(length);

    if (buffer == nullptr) {
        swoole_sys_warning("out of memory");
        return 0;
    }

    switch (sa.addr.ss.sa_family) {
    case AF_INET:
        memcpy(buffer, &sa.addr.inet_v4.sin_port, sizeof(in_port_t));
        memcpy(buffer + sizeof(sa.addr.inet_v4.sin_port), &sa.addr.inet_v4.sin_addr, sizeof(struct in_addr));
        break;
    case AF_INET6:
        memcpy(buffer, &sa.addr.inet_v6.sin6_port, sizeof(in_port_t));
        memcpy(buffer + sizeof(in_port_t), &sa.addr.inet_v6.sin6_addr, sizeof(struct in6_addr));
        break;
    default:
        OPENSSL_assert(0);
        break;
    }

    HMAC(EVP_sha1(), (const void *) cookie_secret, COOKIE_SECRET_LENGTH, buffer, length, result, &result_len);
    OPENSSL_free(buffer);

    memcpy(cookie, result, result_len);
    *cookie_len = result_len;

    return 1;
}

static int swoole_ssl_verify_cookie(SSL *ssl, const uchar *cookie, uint cookie_len) {
    uint result_len = 0;
    uchar result[COOKIE_SECRET_LENGTH];

    swoole_ssl_generate_cookie(ssl, result, &result_len);

    return cookie_len == result_len && memcmp(result, cookie, result_len) == 0;
}
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L
static int swoole_ssl_set_default_dhparam(SSL_CTX *ssl_context) {
    DH *dh;
    static unsigned char dh1024_p[] = {
        0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5, 0x80, 0xE9, 0xCF, 0xDB,
        0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B, 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C,
        0x6C, 0x03, 0x02, 0x76, 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5,
        0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04, 0x9B, 0xE6, 0x77, 0x80,
        0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04, 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8,
        0xF2, 0x9B, 0xA5, 0xDF, 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50,
        0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E, 0x58, 0xFA, 0xFD, 0x49,
        0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA, 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B};

    static unsigned char dh1024_g[] = {0x02};
    dh = DH_new();
    if (dh == nullptr) {
        swoole_warning("DH_new() failed");
        return SW_ERR;
    }

    dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), nullptr);
    dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), nullptr);

    if (dh->p == nullptr || dh->g == nullptr) {
        DH_free(dh);
    }
    SSL_CTX_set_tmp_dh(ssl_context, dh);
    DH_free(dh);
    return SW_OK;
}
#endif

#endif
